#pragma once
#include <unordered_map>
#include <sys/epoll.h>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <vector>
#include "log_macro.hpp"
#include "channel.hpp"

using namespace std;

class epoller {
    static const size_t default_events_size = 1024;
public:
    epoller(size_t events_size = default_events_size)
        :_size(events_size)
    {
        _epfd = epoll_create(1);
        if (_epfd == -1) {
            ERROR_LOG("epoll_create err");
            exit(errno);
        }
    }

    ~epoller() {
        close(_epfd);
    }

    vector<channel *> poller(int timeout = -1) {
        int nfds = ::epoll_wait(_epfd, _evs, _size, timeout);
        if (nfds == -1) {
            ERROR_LOG("epoll_wait err, err string: %s", strerror(errno));
            exit(errno);
        }
        vector<channel *> actives(nfds);
        for(int i = 0; i < nfds; ++i) {
            auto ch_ptr = _channels[_evs[i].data.fd];
            if(ch_ptr == nullptr) {
                ERROR_LOG("epoller::poller, channel not found");
                continue;
            }
            ch_ptr->set_revents(_evs[i].events);
            actives[i] = ch_ptr;
        }
        return actives;
    }

    void update(channel *ch) {
        bool in_channels = channel_in_channels(ch);
        if(!in_channels) {
            _update(EPOLL_CTL_ADD, ch);
            _channels[ch->get_fd()] = ch;
        }
        else {
            _update(EPOLL_CTL_MOD, ch);
        }
    }

    void remove(const channel *ch) {
        if(!channel_in_channels(ch)) {
            return;
        }
        _update(EPOLL_CTL_DEL, ch);
        _channels.erase(ch->get_fd());
    }
    
    int get_epfd() {
        return _epfd;
    }

private:
    bool channel_in_channels(const channel *ch) const {
        return _channels.count(ch->get_fd()) > 0;
    }

    void _update(int op, const channel *ch) const {
        struct epoll_event ev;
        ev.data.fd = ch->get_fd();
        ev.events = ch->get_events();
        if (epoll_ctl(_epfd, op, ch->get_fd(), &ev) == -1) {
            ERROR_LOG("epoll_ctl err, op: %d, fd: %d, err string: %s", op, ch->get_fd(), strerror(errno));
            exit(errno);
        }
    }

private:
    int _epfd;
    struct epoll_event _evs[default_events_size];
    size_t _size;
    unordered_map<int, channel*> _channels;
};

//移除监控
void channel::remove() {
    _epoller->remove(this);
}

void channel::update() {
    _epoller->update(this);  // 告诉epoll来更新channel
}